package com.fzu.geometa.metadata.service.impl;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fzu.geometa.metadata.model.po.Item;
import com.fzu.geometa.metadata.service.XmlService;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Service;
import org.xml.sax.InputSource;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class XmlServiceImpl implements XmlService {
    private final SAXReader reader = new SAXReader();
    private final ObjectMapper objectMapper = new ObjectMapper();

    private List<com.fzu.geometa.metadata.model.po.Namespace> getNamespaces(Document doc) {
        if (doc == null) {
            return null;
        }
        Element root = doc.getRootElement();
        // 获取在根元素声明的命名空间
        List<org.dom4j.Namespace> domNamespaces = root.declaredNamespaces();
        // 转化为自定义的命名空间
        List<com.fzu.geometa.metadata.model.po.Namespace> namespaces = domNamespaces.stream().map((n) -> {
            com.fzu.geometa.metadata.model.po.Namespace ns = new com.fzu.geometa.metadata.model.po.Namespace();
            ns.setPrefix(n.getPrefix());
            ns.setUri(n.getURI());
            return ns;
        }).collect(Collectors.toList());
        return namespaces;
    }

    private List<Item> getItems(Document doc) {
        if (doc == null) {
            return null;
        }
        List<Item> list = new ArrayList<>();
        bfs(doc.getRootElement(), list);
        return list;
    }
    @Override
    public List<com.fzu.geometa.metadata.model.po.Namespace> getNamespaces(String xmlStr) {
        StringReader read = new StringReader(xmlStr);
        return getNamespaces(read);
    }

    @Override
    public List<Item> getItems(String xmlStr) {
        StringReader read = new StringReader(xmlStr);
        return getItems(read);
    }

    // 只需要解析一次
    @Override
    public ItemsAndNs getItemsAndNs(String xmlStr) {
        Document doc;
        try {
             doc = reader.read(new StringReader(xmlStr));
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        ItemsAndNs itemsAndNs = new ItemsAndNs();
        itemsAndNs.setItems(getItems(doc));
        itemsAndNs.setNamespaces(getNamespaces(doc));
        return itemsAndNs;
    }

    @Override
    public ItemsAndNs getItemsAndNs(URL url) {
        Document doc;
        try {
            doc = reader.read(url);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        ItemsAndNs itemsAndNs = new ItemsAndNs();
        itemsAndNs.setItems(getItems(doc));
        itemsAndNs.setNamespaces(getNamespaces(doc));
        return itemsAndNs;
    }

    @Override
    public ItemsAndNs getItemsAndNs(InputStream is) {
        Document doc;
        try {
            doc = reader.read(is);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        ItemsAndNs itemsAndNs = new ItemsAndNs();
        itemsAndNs.setItems(getItems(doc));
        itemsAndNs.setNamespaces(getNamespaces(doc));
        return itemsAndNs;
    }

    @Override
    public List<com.fzu.geometa.metadata.model.po.Namespace> getNamespaces(InputStream is) {
        return getNamespaces(new InputSource(is));
    }

    @Override
    public List<Item> getItems(InputStream is) {
        return getItems(new InputSource(is));
    }

    @Override
    public List<com.fzu.geometa.metadata.model.po.Namespace> getNamespaces(Reader reader) {
        return getNamespaces(new InputSource(reader));
    }

    @Override
    public List<Item> getItems(Reader reader) {
        return getItems(new InputSource(reader));
    }

    @Override
    public List<com.fzu.geometa.metadata.model.po.Namespace> getNamespaces(InputSource is) {
        Document doc;
        try {
            doc = reader.read(is);
        } catch (DocumentException e) {
            e.printStackTrace();
            return null;
        }
        return getNamespaces(doc);
    }

    @Override
    public List<Item> getItems(InputSource is) {
        Document doc;
        try {
            doc = reader.read(is);
        } catch (DocumentException e) {
            e.printStackTrace();
            return null;
        }
        return getItems(doc);
    }


    @Override
    public String getXmlString(List<Item> items, List<com.fzu.geometa.metadata.model.po.Namespace> namespaces) {
        Document doc = getXmlDoc(items, namespaces,null);
        return doc.asXML();
    }

    @Override
    public Document getDocAndIdMap(List<Item> items, List<com.fzu.geometa.metadata.model.po.Namespace> namespaces, Map<Element, Long> map) {
        if (map == null) {
            throw new RuntimeException("Map is empty");
        }
        map.clear();
        return getXmlDoc(items,namespaces,map);
    }

    @Override
    public Element xmlStrToElement(String xmlStr) {
        try {
            return DocumentHelper.parseText(xmlStr).getRootElement();
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }

    // item 和 ns 转为 doc
    private Document getXmlDoc(List<Item> items, List<com.fzu.geometa.metadata.model.po.Namespace> namespaces, Map<Element, Long> eleToId) {
        if (items == null) {
            return null;
        }
        Map<String, String> nsMap = namespaces.stream().collect(Collectors.toMap(com.fzu.geometa.metadata.model.po.Namespace::getPrefix, com.fzu.geometa.metadata.model.po.Namespace::getUri));
        // 由 id 确定 Element
        // 使用 Branch 是为了使根节点添加到文档
        Map<Long,Branch> idToEle = new HashMap<>();
        Document doc = DocumentHelper.createDocument();
        idToEle.put(0L,doc);
        for (Item item : items) {
            idToEle.put(item.getEid(),itemToElement(item,nsMap));
        }

        // 按order排序
        items = items.stream()
                .sorted(Comparator.comparing(Item::getOrder))
                .collect(Collectors.toList());

        // 还原 xml
        for (Item item : items) {
            Long eid = item.getEid();
            Long pid = item.getPid();
            // 父子节点相连
            idToEle.get(pid).add(idToEle.get(eid));
        }

        // 根节点添加命名空间
        Element root = doc.getRootElement();
        for (com.fzu.geometa.metadata.model.po.Namespace ns : namespaces) {
            root.addNamespace(ns.getPrefix(),ns.getUri());
        }

        if (eleToId != null) {
            eleToId.clear();
            idToEle.remove(0L);
            idToEle.forEach((k,v)-> eleToId.put((Element) v,k));
        }
        return doc;
    }

    private Element itemToElement(Item item, Map<String, String> nsMap) {
        if (item == null) {
            return null;
        }
        String nsPre = item.getNamespace();
        Element element;
        if (StringUtils.isBlank(nsPre)) {
            // 没有命名空间
            element = DocumentHelper.createElement(item.getName());
        } else {
            // 设置命名空间
            QName qName = new QName(item.getName(),
                    DocumentHelper.createNamespace(nsPre,nsMap.get(nsPre)));
            element = DocumentHelper.createElement(qName);
        }

        // 设置属性
        if (item.getAttributes() != null) {
            String s = item.getAttributes().toString();
            try {
                JsonNode jsonNode = objectMapper.readTree(s);
                Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
                while (fields.hasNext()) {
                    Map.Entry<String, JsonNode> entry = fields.next();
                    element.addAttribute(entry.getKey(),entry.getValue().asText());
                }
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        // 设置文本
        if (item.getValue() != null) {
            element.setText(item.getValue());
        }
        return element;
    }

    // 广度优先遍历，生成item
    private void bfs(Element root, List<Item> list) {
        if (root == null || list == null) {
            return;
        }
        // 清空数据集
        list.clear();
        Queue<Element> que = new LinkedList<>();
        que.offer(root);
        // 记录 Element 的 id
        Map<Element, Long> map = new HashMap<>();
        // 根节点的父节点id设为0
        map.put(null, 0L);
        // order 队列，对应元素在父节点中的次序
        Queue<Integer> orderQue = new LinkedList<>();
        orderQue.offer(0);
        while (!que.isEmpty()) {
            int len = que.size();
            while (len-- > 0) {
                Element elem = que.poll();
                // 创建 item
                Item item = new Item();
                item.setEid(IdWorker.getId());
                item.setName(elem.getName());
                item.setPid(map.get(elem.getParent()));
                // 设置命名空间
                String prefix = elem.getNamespacePrefix();
                if (!StringUtils.isBlank(prefix)){
                    item.setNamespace(prefix);
                }
                item.setOrder(orderQue.poll());
                item.setValue(elem.getTextTrim().equals("") ? null : elem.getTextTrim());
                // 添加属性
                List<Attribute> attributes = elem.attributes();
                if (attributes.size() > 0) {
                    ObjectNode objectNode = objectMapper.createObjectNode();
                    for (Attribute a : attributes) {
                        objectNode.put(a.getQualifiedName(), a.getValue());
                    }
                    item.setAttributes(objectNode.toString());
                }
                // 元素加入 map
                map.put(elem, item.getEid());
                // 加入数据集
                list.add(item);
                // 子节点入队
                int order = 0;
                for (Element c : elem.elements()) {
                    que.offer(c);
                    orderQue.offer(order++);
                }
            }
        }
    }
}
